package rep

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"strings"

	"gitcode.com/deyiyangyang/showen-lang/evl"
	"gitcode.com/deyiyangyang/showen-lang/lex"
	"gitcode.com/deyiyangyang/showen-lang/prs"
	"gitcode.com/deyiyangyang/showen-lang/typ"
)

// REPL implements an interactive command-line interface
func REPL() {
	// Create a global environment
	env := typ.NewEnvironment()
	typ.DefineBuiltins(env)

	// Check if input is coming from a pipe
	stat, _ := os.Stdin.Stat()
	isPiped := (stat.Mode() & os.ModeCharDevice) == 0

	// If not reading from a pipe, print welcome message
	if !isPiped {
		// Print welcome message
		fmt.Println("Welcome to Showen-Lang, a simple Scheme interpreter.")
		fmt.Println("Type expressions and press Enter to evaluate them.")
		fmt.Println("Type 'exit' to quit.")
	}

	// Create input reader
	reader := bufio.NewReader(os.Stdin)

	// If reading from a pipe, process single line input and exit
	if isPiped {
		// Read all input
		input, err := io.ReadAll(reader)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Error reading input:", err)
			return
		}

		// Remove newlines and whitespace
		inputStr := strings.TrimSpace(string(input))

		// Split input by lines
		lines := strings.Split(inputStr, "\n")

		// Process each line
		for _, line := range lines {
			line = strings.TrimSpace(line)
			if line == "exit" {
				break
			}

			// Parse and evaluate input
			result, err := EvaluateInput(line, env)
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error:", err)
				continue
			}

			// Print result
			if result != nil && result != typ.Nil {
				fmt.Println(result.String())
			}
		}
		return
	}

	// Interactive mode
	for {
		// Show prompt
		fmt.Print(">>> ")

		// Read input
		input, err := reader.ReadString('\n')
		if err != nil {
			fmt.Fprintln(os.Stderr, "Error reading input:", err)
			continue
		}

		// Remove newlines and whitespace
		input = strings.TrimSpace(input)

		// Check if exiting
		if input == "exit" {
			fmt.Println("Goodbye!")
			break
		}

		// Process multi-line input
		fullInput := input
		parenCount := strings.Count(input, "(") - strings.Count(input, ")")

		// Continue reading input when parentheses don't match
		for parenCount > 0 {
			fmt.Print("... ")
			line, err := reader.ReadString('\n')
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error reading input:", err)
				break
			}
			fullInput += " " + strings.TrimSpace(line)
			parenCount += strings.Count(line, "(") - strings.Count(line, ")")
		}

		// Parse and evaluate input
		result, err := EvaluateInput(fullInput, env)
		if err != nil {
			fmt.Fprintln(os.Stderr, "Error:", err)
			continue
		}

		// Print result
		if result != nil && result != typ.Nil {
			fmt.Println(result.String())
		}
	}
}

// EvaluateInput parses and evaluates the input Scheme code
func EvaluateInput(input string, env *typ.Environment) (typ.Object, error) {
	// Create lexer
	tokenizer := lex.NewTokenizer(input)

	// Create parser
	parser, err := prs.NewParser(tokenizer)
	if err != nil {
		return nil, err
	}

	// Parse expression
	expr, err := parser.Parse()
	if err != nil {
		return nil, err
	}

	// Evaluate expression
	result := evl.Eval(expr, env)

	// Check for errors
	if str, ok := result.(*typ.String); ok && strings.HasPrefix(str.Value, "Error:") {
		return nil, fmt.Errorf("%s", str.Value)
	}

	return result, nil
}
